package com.rtf.framework.dao.config.datasource;

import com.rtf.framework.common.json.JsonMapper;
import com.rtf.framework.common.util.UtilBean;
import com.rtf.framework.dao.config.AppDaoProperties;
import com.rtf.framework.dao.config.builder.DruidDataSourceBuilder;
import com.rtf.framework.dao.config.datasource.support.ShardingRuleConfigurationAware;
import com.rtf.framework.dao.dynamic.DynamicDataSource;
import com.google.common.collect.Lists;
import com.google.common.collect.Maps;
import lombok.extern.slf4j.Slf4j;
import org.apache.shardingsphere.api.config.masterslave.MasterSlaveRuleConfiguration;
import org.apache.shardingsphere.api.config.sharding.ShardingRuleConfiguration;
import org.apache.shardingsphere.shardingjdbc.api.ShardingDataSourceFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.util.Assert;

import javax.sql.DataSource;
import java.sql.SQLException;
import java.util.*;
import java.util.stream.Collectors;

/**
 * 数据库连接池通用配置信息，存储数据库连接池通用的配置参数等。
 * @Author : liupeng
 * @Date : 2018/7/27 14:36
 * @Modified By
 */
@Slf4j
@Configuration
@ConditionalOnProperty(name = AppDaoProperties.PREFIX + ".datasource.enabled", matchIfMissing = true, havingValue = "true")
public class AppDataSourceAutoConfiguration {

    @Autowired
    private AppDaoProperties appDaoProperties;

    public AppDataSourceAutoConfiguration() {
        log.debug("初始化{}", this.getClass().getSimpleName());
    }


    /**
     * 获取数据库连接池，支持单源和多源数据库连接
     */
//    @Bean(destroyMethod = "close")
    @Bean
    public DataSource dataSource() {
        AppDaoProperties.DataSource dataSourceProperties = appDaoProperties.getDatasource();

        if( appDaoProperties.getSharding().isEnabled() ){
            log.debug("初始化数据库连接,配置信息为: {}", JsonMapper.toJSONString(appDaoProperties.getSharding().getDatasource(), true));
            // 开启分片策略之后，禁用jpa的更新
            appDaoProperties.getJpa().setDdlAuto("none") ;
            return buildShardingDataSource() ;
        }else if( appDaoProperties.getDynamic().isEnabled() ){
            log.debug("初始化数据库连接,配置信息为: {}", JsonMapper.toJSONString(appDaoProperties.getDynamic().getMultiple(), true));
            return buildDynamicDataSource() ;
        }

        log.debug("初始化数据库连接,配置信息为: {}", JsonMapper.toJSONString(dataSourceProperties, true));
        return DruidDataSourceBuilder.getDataSource( dataSourceProperties ) ;
    }

    /**
     * 获取默认
     *
     * @return
     */
    private DataSource buildDynamicDataSource() {
        DynamicDataSource dataSource = new DynamicDataSource();
        String masterName = appDaoProperties.getDynamic().getMasterName();
        Map<String, AppDaoProperties.DataSource> dataSourceMap = appDaoProperties.getDynamic().getMultiple();
        AppDaoProperties.DataSource masterDataSourceProperties = dataSourceMap.get(masterName);
        dataSource.setDefaultTargetDataSource(DruidDataSourceBuilder.getDataSource(masterDataSourceProperties));
        Map<Object, Object> targetDataSources = new HashMap<>();
        dataSourceMap.forEach((key, value) -> targetDataSources.put(key, DruidDataSourceBuilder.getDataSource(value)));
        dataSource.setTargetDataSources(targetDataSources);
        return dataSource;
    }

    /**
     * 构建分片数据源
     * @return
     */
    private DataSource buildShardingDataSource(){
        Assert.hasText( appDaoProperties.getSharding().getShardingRule(), "分片规则类不能为空" ) ;

        // 1. 获取datasource
        Map<String, List<AppDaoProperties.DataSource>> dataSourcesMap = appDaoProperties.getSharding().getDatasource() ;

        Assert.hasText( appDaoProperties.getSharding().getDefaultDataSource(), "默认的数据源不能为空" ) ;

        Assert.isTrue( dataSourcesMap.containsKey( appDaoProperties.getSharding().getDefaultDataSource() ),
                "默认的数据源必须为: " + JsonMapper.toJSONString( dataSourcesMap.keySet() ) + "其中一个") ;

        // 2. 构建数据源
        Map<String,DataSource> allDataSource = Maps.newHashMap() ;
        // 分组的数据源列表
        Map<String, List<DataSource>> targetDataSources = Maps.newHashMap() ;
        dataSourcesMap.forEach((key, dataSources) -> {
            // 2.1 遍历每个数据源，分别创建数据库连接池
            List<DataSource> dataSourceList = Lists.newArrayList() ;

            for (AppDaoProperties.DataSource dataSourceConfig : dataSources) {
                // 2.2 检查相同名称的数据源是否存在
                if( allDataSource.containsKey( dataSourceConfig.getName() ) ){
                    throw new IllegalArgumentException( "数据源: " + dataSourceConfig.getName() + " 已经存在" ) ;
                }

                // 2.3 构建数据库连接池
                DataSource dataSourceInstance = DruidDataSourceBuilder.getDataSource( dataSourceConfig ) ;

                dataSourceList.add( dataSourceInstance ) ;
                allDataSource.put( dataSourceConfig.getName() , dataSourceInstance ) ;
            }

            targetDataSources.put( key , dataSourceList ) ;
        } ) ;

        // 3. 分片规则配置
        ShardingRuleConfiguration shardingRuleConfiguration = null ;
        try {
            // 3.1 获取分片规则
            ShardingRuleConfigurationAware shardingRuleConfigurationAware = (ShardingRuleConfigurationAware)
                    UtilBean.instantiateClass( Class.forName( appDaoProperties.getSharding().getShardingRule() ) ) ;

            // 3.2 设置分片配置，获取分片规则
            shardingRuleConfigurationAware.setShardingDataSourceConfig( appDaoProperties.getSharding() ) ;
            shardingRuleConfiguration = shardingRuleConfigurationAware.getShardingRuleConfiguration() ;

            if( shardingRuleConfiguration==null ){
                throw new IllegalArgumentException("数据库分片规则类不能为空") ;
            }

            // 3.3 创建数据源
            List<MasterSlaveRuleConfiguration> masterSlaveRuleConfigurations = Lists.newArrayList() ;
            for (Map.Entry<String, List<AppDaoProperties.DataSource>> entry : dataSourcesMap.entrySet()) {
                // 如果同时存在主备，则设置主备服务
                if( hasMasterSlave( entry.getValue() ) ){
                    MasterSlaveRuleConfiguration masterSlaveRuleConfiguration = new MasterSlaveRuleConfiguration(
                            entry.getKey() , getMasterDataSourceName( entry.getValue() ) , getSlaveDataSourceNames( entry.getValue() )
                    ) ;
                    masterSlaveRuleConfigurations.add( masterSlaveRuleConfiguration ) ;
                }
            }
            if( masterSlaveRuleConfigurations.size() > 0 ){
                shardingRuleConfiguration.setMasterSlaveRuleConfigs( masterSlaveRuleConfigurations ) ;
            }

            // 3.4 设置默认数据源
            shardingRuleConfiguration.setDefaultDataSourceName( appDaoProperties.getSharding().getDefaultDataSource() ) ;

        } catch (ClassNotFoundException e) {
            log.error("初始化配置规则失败" , e);
        }

        // 4. 创建数据库
        DataSource shardingDataSource = null ;
        try {
            // 4.1 配置分片属性
            Properties shardingProperties = new Properties() ;
            shardingProperties.put("sql.show" , appDaoProperties.getSharding().getShowSql()) ;

            // 4.2 创建分片代理数据源
            shardingDataSource = ShardingDataSourceFactory.createDataSource( allDataSource , shardingRuleConfiguration , shardingProperties ) ;
        } catch (SQLException e) {
            e.printStackTrace();
            log.error("创建分片数据库失败" , e);
        }

        return shardingDataSource ;
    }

    /**
     * 是否同时存在master和slave
     * @param dataSources
     * @return
     */
    public boolean hasMasterSlave(List<AppDaoProperties.DataSource> dataSources){
        boolean hasMaster = false ;
        boolean hasSlave = false ;

        for (AppDaoProperties.DataSource dataSource : dataSources) {
            if( dataSource.getMaster() && !hasMaster ){
                hasMaster = true ;
            }
            if( !dataSource.getMaster() && !hasSlave ){
                hasSlave = true ;
            }
        }

        return hasMaster && hasSlave ;
    }

    /**
     * 获取master数据源
     * @param dataSources
     * @return
     */
    public String getMasterDataSourceName(List<AppDaoProperties.DataSource> dataSources ){
        List<AppDaoProperties.DataSource> masterDataSources = dataSources.stream().filter( dataSource -> dataSource.getMaster() )
                .collect( Collectors.toList() ) ;

        Assert.isTrue( masterDataSources!=null && masterDataSources.size()==1 , "多个数据源中必须存在且仅存在一个master" ); ;

        return masterDataSources.get(0).getName() ;
    }

    /**
     * 获取master数据源
     * @param dataSources
     * @return
     */
    public List<String> getSlaveDataSourceNames(List<AppDaoProperties.DataSource> dataSources ){
        // 1. 如果只有一个元素，则使用master作为slave数据源
        if( dataSources.size()==1 ){
            return Lists.newArrayList( dataSources.get(0).getName() ) ;
        }

        // 2. 如果slave数据源有多个，则选择一个slave数据源
        List<String> slaveDataSourceNames = dataSources.stream().filter( dataSource -> !dataSource.getMaster() )
                .map( dataSource -> dataSource.getName() ).collect( Collectors.toList() ) ;

        Assert.notEmpty( slaveDataSourceNames , "slave数据源不能为空" ) ;

        return slaveDataSourceNames ;
    }

}
